package org.springframework.beans.factory.xml;
import cn.hutool.core.util.StrUtil;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * 读取配置在xml文件中的bean定义信息
 *
 * @author derekyi
 * @date 2020/11/26
 */
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

	public static final String BEAN_ELEMENT = "bean";
	public static final String PROPERTY_ELEMENT = "property";
	public static final String ID_ATTRIBUTE = "id";
	public static final String NAME_ATTRIBUTE = "name";
	public static final String CLASS_ATTRIBUTE = "class";
	public static final String VALUE_ATTRIBUTE = "value";
	public static final String REF_ATTRIBUTE = "ref";
	public static final String INIT_METHOD_ATTRIBUTE = "init-method";
	public static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method";
	public static final String SCOPE_ATTRIBUTE = "scope";
	public static final String LAZYINIT_ATTRIBUTE = "lazyInit";
	public static final String BASE_PACKAGE_ATTRIBUTE = "base-package";
	public static final String COMPONENT_SCAN_ELEMENT = "component-scan";
	//我们发现这里是BeanDefinitionRegistry，他是ioc容器的接口，我们可以看上面类图，左边的
	public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
		//传入一个ioc容器需要，父类构造方法，传入一个资源加载器
		super(registry);
	}

	public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
		super(registry, resourceLoader);
	}

	@Override
	public void loadBeanDefinitions(String location) throws BeansException {

		//构造方法哪里创建了一个resourceLoader对象，我们这里把他拿来
		ResourceLoader resourceLoader = getResourceLoader();
		//得到了一个资源对象，我们对传入的字符串分类，构建不同资源子类对象（url，src，classpath）
		Resource resource = resourceLoader.getResource(location);
		//资源加载器加载资源（3种子类资源）
		loadBeanDefinitions(resource);

	}

	@Override
	public void loadBeanDefinitions(Resource resource) throws BeansException {
		//如何读取，先得到资源的输入流，读取资源
		try {
			//得到一个输入流
			InputStream inputStream = resource.getInputStream();
			try {
				//这里读取数据
				doLoadBeanDefinitions(inputStream);

			} finally {
				inputStream.close();
			}
		} catch (IOException | DocumentException ex) {
			throw new BeansException("IOException parsing XML document from " + resource, ex);
		}
	}

	protected void doLoadBeanDefinitions(InputStream inputStream) throws DocumentException {

		//解析xml文件
		SAXReader reader = new SAXReader();
		//先去读取当前的输入流，封装成Document（文档）对象，就是xml的文档
		Document document = reader.read(inputStream);
		//得到文档根标记
		Element root = document.getRootElement();

		//解析context:component-scan标签并扫描指定包中的类，提取类信息，组装成BeanDefinition
		//componentScan不为空，说明xml文件里有component-scan标签，我们按照标签在下面去扫描
		Element componentScan = root.element(COMPONENT_SCAN_ELEMENT);


		//这里做了一个判断，看看根标记里是否有一个标记是COMPONENT_SCAN_ELEMENT（componentScan）
		if (componentScan != null) {
			//componentScan里我们要得到base-package（包名称），看笔记的图片里，就是这么名称
			String scanPath = componentScan.attributeValue(BASE_PACKAGE_ATTRIBUTE);
			if (StrUtil.isEmpty(scanPath)) {
				//扫描路径为空就抛出一个一次
				throw new BeansException("The value of base-package attribute can not be empty or null");
			}
			//开始扫描
			scanPackage(scanPath);
			//这里完成后，所有的component-scan下定义的包里的（加了注解的）类已经被定义为beandefinition，并且存入ioc了
		}



		//BEAN_ELEMENT，bean标记。把所有bean标记标记的读取了进来，一个xml文件里所有的bean标记的类都读进来
		List<Element> beanList = root.elements(BEAN_ELEMENT);


		for (Element bean : beanList) {//这个bean是Element（文档）对象，里面有很多数据
			String beanId = bean.attributeValue(ID_ATTRIBUTE);//得到beanid
			String beanName = bean.attributeValue(NAME_ATTRIBUTE);//得到beanName，关键
			String className = bean.attributeValue(CLASS_ATTRIBUTE);//得到类名。关键
			String initMethodName = bean.attributeValue(INIT_METHOD_ATTRIBUTE);//初始化方法
			String destroyMethodName = bean.attributeValue(DESTROY_METHOD_ATTRIBUTE);//销毁方法
			String beanScope = bean.attributeValue(SCOPE_ATTRIBUTE);//这个是作用域，单例多利，下面做判断
			String lazyInit = bean.attributeValue(LAZYINIT_ATTRIBUTE);
			Class<?> clazz;
			try {
				clazz = Class.forName(className);
				//这里是通过类的全路径得到这个具体的类，类就有了

			} catch (ClassNotFoundException e) {
				throw new BeansException("Cannot find class [" + className + "]");
			}
			//id优先于name
			//这里做一个判断，如果beanId不为空，就把他变成一个beanName
			//这里就涉及到了里面的一个重点了，beanId和beanName（当你设置后有什么用处）
			beanName = StrUtil.isNotEmpty(beanId) ? beanId : beanName;
			if (StrUtil.isEmpty(beanName)) {
				//如果id和name都为空，将类名的第一个字母转为小写后作为bean的名称，重要！
				beanName = StrUtil.lowerFirst(clazz.getSimpleName());
			}//总之在这里，bean的name就有了。两个关键因素都有了


			BeanDefinition beanDefinition = new BeanDefinition(clazz);//这样就是之前的思路一直循环
			//这里存一些初始化，消耗，懒加载方法
			beanDefinition.setInitMethodName(initMethodName);
			beanDefinition.setDestroyMethodName(destroyMethodName);
			beanDefinition.setLazyInit(Boolean.parseBoolean(lazyInit));

			if (StrUtil.isNotEmpty(beanScope)) {//默认是单例，这里判断一下
				beanDefinition.setScope(beanScope);//不是单例，设置一下
			}

			List<Element> propertyList = bean.elements(PROPERTY_ELEMENT);


			//property属性，把里面所有的属性都读取进来了，循环赋予属性
			for (Element property : propertyList) {
				//这里要对照xml文件看
				String propertyNameAttribute = property.attributeValue(NAME_ATTRIBUTE);//对应配置文件的name
				String propertyValueAttribute = property.attributeValue(VALUE_ATTRIBUTE);
				String propertyRefAttribute = property.attributeValue(REF_ATTRIBUTE);

				if (StrUtil.isEmpty(propertyNameAttribute)) {
					throw new BeansException("The name attribute cannot be null or empty");
				}

				Object value = propertyValueAttribute;
				if (StrUtil.isNotEmpty(propertyRefAttribute)) {
					value = new BeanReference(propertyRefAttribute);//value为空，就是ref类型，直接覆盖
				}
				PropertyValue propertyValue = new PropertyValue(propertyNameAttribute, value);
				beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
			}
			if (getRegistry().containsBeanDefinition(beanName)) {
				//beanName不能重名
				throw new BeansException("Duplicate beanName[" + beanName + "] is not allowed");
			}
			//注册BeanDefinition
			getRegistry().registerBeanDefinition(beanName, beanDefinition);
		}
	}

	/**
	 * 扫描注解Component的类，提取信息，组装成BeanDefinition
	 *
	 * @param scanPath
	 */
	private void scanPackage(String scanPath) {

		String[] basePackages = StrUtil.splitToArray(scanPath, ',');
		//扫描里面的事情是ClassPathBeanDefinitionScanner类的doScan做的
		//传入了一个参数getRegistry（）。其实这个Registry就是一个ioc容器（基础的），我们这里相当于把ioc容器给他了
		//我们要在ioc容器里注册扫描到的类的beanDefinition对象
		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(getRegistry());
		//在这里要扫描包，并且封装对象了
		scanner.doScan(basePackages);
	}
}
